DOM に変更が走るかどうか監視を行えるものにMutationObserverAPI があります。使うにはまずインスタンスを作る必要があります。
const observer = new MutationObserver(callback);
引数にはcallbackが必要です。これは DOM に変更が走った時に発火するコールバック関数です。コールバック発火時この関数にはMutationRecordというオブジェクトが配列渡ってきます。なぜ配列かというとそのタイミングで置きたすべての記録が渡される為です。MutationRecordにはどういう変更が走ったのか(type)やそれぞれのtypeで役立つ情報などが渡ってきます。
監視を始めるには作ったインスタンスobserverのobserveを対象の要素を引数に実行します。
observer.observe(node, {/* ... */});
監視を終えたい時はdisconnectメソッドを実行します。これを実行すると今までにobserveへ渡した要素についてすべて監視が解除されます。
observer.disconnect();
MutationObserverInit
MutationObserver#observeする時、どういったものを監視するのかを指定する必要があります。このオプションで指定できる値は以下の7つです。
childListsubtreeattributesattributeOldValueattributeFiltercharacterDatacharacterDataOldValue
この中で、attributesとcharacterData、childListの内1つは必ずtrueで設定しなければなりません。
ざっと見れば7つと分かりづらいですが、グループ分して以下の3つに分けて考えても良いと思います。
childListと`subTreeattributesとattributeOldValue、attributeFiltercharacterDataとcharacterDataOldValue
childList と subTree
要素が追加された、削除されたを監視したい場合はchildListをtrueで置きます。例えば自身がelementだとして、element.appendChildやelement.removeChildなどが呼ばれた時にコールバックが発火します。MutationRecordへは要素が追加された場合はaddedNodesに、削除された場合はremovedNodesにそれぞれ対象の要素が渡されます。
上記のelementように監視対象が自分自身だけの場合ならこれでも良いですが、子が持つ要素も同じように監視したい場合にsubTreeをtrueにします。これで何らかの手段である子要素に子要素ができた時にもコールバックを走らせれます。
observer.observe(item, {
childList: true,
subtree: true,
});
attributes と attributeOldValue、 attributeFilter
要素の属性が変わるかどうか監視したい場合はattributesをtrueで置きます。このtypeでコールバックが発火した時のみ、attributeNameに値が入ります。例えばstyle属性を変更して発火したのであればstyleのような感じです。
後の2つはオプショナルです。
attributeOldValueをtrueにすると、変更があった際のMutationRecordのoldValueに対象の属性の古い値が入ってきます。
attributeFilterは属性を絞り込みたい場合に使います。例えば「value属性は監視したいけど、id属性の監視は要らないや」という場合以下のように設定します。
observer.observe(item, {
attributes: true,
attributeOldValue: true,
attributeFilter: ['id'],
});
characterData と characterDataOldValue
テキストが変わったかどうかを監視したい場合はcharacterDataをtrueで置きます。characterDataOldValueはオプショナルでtrueにすると、attributeOldValueのようにoldValueプロパティに変更前のテキストが入ってきます、
observer.observe(node.childNodes[0], {
characterData: true,
characterDataOldValue: true,
});
このように監視した状態で以下のようにテキストを更新するとコールバックが発火します。
node.childNodes[0].nodeValue = 'update text';
実際に動いてる様子はCodeSandboxで見ることができます。